﻿/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.Diagnostics;

namespace STSdb.General.Reflection
{
    public class PropertyReflector<TInstance, TProperty>
    {
        private Func<TInstance, TProperty> getDelegate;
        private Action<TInstance, TProperty> setDelegate;

        public string PropertyName { get; private set; }

        public PropertyReflector(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");

            PropertyName = propertyName;

            getDelegate = MakeGet();
            setDelegate = MakeSet();
        }

        private Func<TInstance, TProperty> MakeGet()
        {
            Type nestedType = typeof(TInstance);
            var instanceParam = Expression.Parameter(typeof(TInstance), "obj");
            Expression get = instanceParam;
            foreach (string property in PropertyName.Split('.'))
            {
                PropertyInfo propertyInfo = nestedType.GetProperty(property);
                if (propertyInfo == null)
                    throw new ArgumentException(string.Format("Property {0} {1} does not exists ", PropertyName, nestedType.ToString()));

                if (!propertyInfo.CanRead)
                    return null;

                MethodInfo methodInfo = propertyInfo.GetGetMethod();
                if (methodInfo == null)
                    return null;

                get = Expression.MakeMemberAccess(get, propertyInfo);
                nestedType = methodInfo.ReturnType;
            }
            var lambdaGet = Expression.Lambda<Func<TInstance, TProperty>>(get, instanceParam);

            return lambdaGet.Compile();
        }

        private Action<TInstance, TProperty> MakeSet()
        {
            Type nestedType = typeof(TInstance);
            var instanceParam = Expression.Parameter(typeof(TInstance), "obj");
            Expression get = instanceParam;
            PropertyInfo propertyInfo;
            MethodInfo methodInfo;
            string property;
            string[] properties = PropertyName.Split('.');
            for (int i = 0; i < properties.Length - 1; i++)
            {
                property = properties[i];
                propertyInfo = nestedType.GetProperty(property);
                if (propertyInfo == null)
                    throw new ArgumentException(string.Format("Property {0} {1} does not exists ", PropertyName, nestedType.ToString()));

                if (!propertyInfo.CanRead)
                    return null;

                methodInfo = propertyInfo.GetGetMethod();
                if (methodInfo == null)
                    return null;

                get = Expression.MakeMemberAccess(get, propertyInfo);
                nestedType = methodInfo.ReturnType;
            }

            property = properties[properties.Length - 1];
            propertyInfo = nestedType.GetProperty(property);
            if (propertyInfo == null)
                throw new ArgumentException(string.Format("Property {0} {1} does not exists ", PropertyName, nestedType.ToString()));

            if (!propertyInfo.CanWrite)
                return null;

            methodInfo = propertyInfo.GetSetMethod();
            if (methodInfo == null)
                return null;

            //get = Expression.MakeMemberAccess(get, propertyInfo);
            var valueParam = Expression.Parameter(typeof(TProperty), "value");
            var set = Expression.Call(get, methodInfo, valueParam);
            var lambdaSet = Expression.Lambda<Action<TInstance, TProperty>>(set, instanceParam, valueParam);

            return lambdaSet.Compile();
        }

        public TProperty GetValue(TInstance instance)
        {
            return getDelegate(instance);
        }

        public void SetValue(TInstance instance, TProperty value)
        {
            setDelegate(instance, value);
        }
    }
}
